home *** CD-ROM | disk | FTP | other *** search
/ Delphi Magazine Collection 2001 / Delphi Magazine Collection 20001 (2001).iso / DISKS / Issue26 / survive / MYDS.PAS < prev    next >
Encoding:
Pascal/Delphi Source File  |  1997-08-21  |  18.1 KB  |  590 lines

  1. unit MyDS;
  2.  
  3. interface
  4.  
  5. uses
  6.   Classes, DB, DBConsts, SysUtils;
  7.  
  8. type
  9.   PExtraRecInfo = ^TExtraRecInfo;
  10.   TExtraRecInfo = record
  11.       RecordNumber: LongInt;
  12.       BookmarkFlag: TBookmarkFlag;
  13.     end;
  14.  
  15.   TBookmarkInfo = LongInt;
  16.  
  17.   TMyDataSet = class(TDataSet)
  18.     private
  19.       FBookmarkOffset: LongInt;  { Offset to bookmark data in recbuf }
  20.       FCalcFieldsOffset: Word;   { Offset to calculated fields data }
  21.       FCursorOpen: Boolean;      { True if cursor is open }
  22.       FInternalFile: file;       { File variable }
  23.       FRecSize: Word;            { Physical size of record }
  24.       FRecBufSize: Word;         { Total size of recbuf }
  25.       FExtraRecInfoOffset: Word; { Offset to extra rec info in recbuf }
  26.       FTableName: TFileName;     { External filename to open }
  27.       FNullFlagsOffset: Word;    { Offset to null flags in recbuf }
  28.     protected
  29.  
  30.       { basic file reading and navigation }
  31.       function AllocRecordBuffer: PChar; override;
  32.       procedure FreeRecordBuffer(var Buffer: PChar); override;
  33.       function GetCurrentRecord(Buffer: PChar): Boolean; override;
  34.       function GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult; override;
  35.       function GetRecordCount: Integer; override;
  36.       function GetRecordSize: Word; override;
  37.       function GetRecNo: Integer; override;
  38.       procedure InternalClose; override;
  39.       procedure InternalFirst; override;
  40.       procedure InternalLast; override;
  41.       procedure InternalOpen; override;
  42.       procedure InternalRefresh; override;
  43.       function IsCursorOpen: Boolean; override;
  44.  
  45.       { bookmarks }
  46.       function BookmarkValid(Bookmark: TBookmark): Boolean; override;
  47.       function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer; override;
  48.       procedure GetBookmarkData(Buffer: PChar; Data: Pointer); override;
  49.       function GetBookmarkFlag(Buffer: PChar): TBookmarkFlag; override;
  50.       procedure SetBookmarkData(Buffer: PChar; Data: Pointer); override;
  51.       procedure SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag); override;
  52.       procedure InternalGotoBookmark(Bookmark: Pointer); override;
  53.       procedure InternalSetToRecord(Buffer: PChar); override;
  54.  
  55.       { basic file modification }
  56.       procedure InternalInitRecord(Buffer: PChar); override;
  57.       procedure InternalEdit; override;
  58.       procedure InternalDelete; override;
  59.       procedure InternalPost; override;
  60.  
  61.       { field component stuff }
  62.       procedure InternalInitFieldDefs; override;
  63.       function GetFieldData(Field: TField; Buffer: Pointer): Boolean; override;
  64.       procedure SetFieldData(Field: TField; Buffer: Pointer); override;
  65.       procedure InternalAddRecord(Buffer: Pointer; Append: Boolean); override;
  66.  
  67.       { calculated fields }
  68.       procedure ClearCalcFields(Buffer: PChar); override;
  69.     protected
  70.       { our own custom stuff }
  71.       FieldOffsets: TList;
  72.     public
  73.       constructor Create(AOwner: TComponent); override;
  74.       destructor Destroy; override;
  75.       property RecordSize: Word read GetRecordSize;  { from TDataSet }
  76.       property TableName: TFileName read FTableName write FTableName;
  77.     end;
  78.  
  79. implementation
  80.  
  81. constructor TMyDataSet.Create(AOwner: TComponent);
  82. begin
  83.   inherited Create(AOwner);
  84.   FieldOffsets := TList.Create;
  85. end;
  86.  
  87. destructor TMyDataSet.Destroy;
  88. begin
  89.   FieldOffsets.Free;
  90.   inherited Destroy;
  91. end;
  92.  
  93. function TMyDataSet.AllocRecordBuffer: PChar;
  94. begin
  95.   Result := StrAlloc(FRecBufSize);
  96.   FillChar(Result^, FRecBufSize, #0);
  97. end;
  98.  
  99. function TMyDataSet.BookmarkValid(Bookmark: TBookmark): Boolean;
  100. var
  101.   DelFlag: Byte;
  102. begin
  103.   Result := Assigned(Bookmark) and
  104.             (TBookmarkInfo(Bookmark^) > 0) and
  105.             (TBookmarkInfo(Bookmark^) <= RecordCount);
  106.   if Result then begin
  107.     CursorPosChanged;  { physical position no longer matches logical position }
  108.     try
  109.       Seek(FInternalFile, TBookmarkInfo(Bookmark^) * FRecSize);
  110.       BlockRead(FInternalFile, DelFlag, 1);
  111.       Result := DelFlag = 0;  { check for a deleted record }
  112.     except
  113.       Result := False;
  114.     end;
  115.   end;
  116. end;
  117.  
  118. procedure TMyDataSet.ClearCalcFields(Buffer: PChar);
  119. begin
  120.   FillChar(Buffer[FCalcFieldsOffset], CalcFieldsSize, 0);
  121. end;
  122.  
  123. function TMyDataSet.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer;
  124. begin
  125.   { bookmarks are equal if they are both nil or they both have the same value }
  126.   if Bookmark1 = Bookmark2 then
  127.     Result := 0
  128.   else begin
  129.     Result := 1;
  130.     if Assigned(Bookmark1) and Assigned(Bookmark2) then
  131.       if TBookmarkInfo(Bookmark1^) = TBookmarkInfo(Bookmark2^) then
  132.         Result := 0;
  133.   end;
  134. end;
  135.  
  136. procedure TMyDataSet.FreeRecordBuffer(var Buffer: PChar);
  137. begin
  138.   StrDispose(Buffer);
  139. end;
  140.  
  141. procedure TMyDataSet.GetBookmarkData(Buffer: PChar; Data: Pointer);
  142. begin
  143.   Move(Buffer[FBookmarkOffset], Data^, BookmarkSize);
  144. end;
  145.  
  146. function TMyDataSet.GetBookmarkFlag(Buffer: PChar): TBookmarkFlag;
  147. begin
  148.   Result := PExtraRecInfo(Buffer + FExtraRecInfoOffset).BookmarkFlag;
  149. end;
  150.  
  151. function TMyDataSet.GetCurrentRecord(Buffer: PChar): Boolean;
  152. begin
  153.   Result := False;
  154.   if not IsEmpty then begin
  155.     Result := True;
  156.     Move(ActiveBuffer^, Buffer^, FRecSize);  {not sure here, buffer may contain internal data}
  157.   end;
  158. end;
  159.  
  160. function TMyDataSet.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
  161. { Get the data for the given field from the active buffer and stick it
  162.   in the given buffer.  Return False if the field value is null; otherwise
  163.   return True. Buffer may be nil if TDataSet is checking for null only. }
  164. var
  165.   Offset,
  166.   DataSize: Integer;
  167.   NullFlags: ^LongInt;
  168.   TimeStamp: TTimeStamp;  { TTimeStamp declared in SysUtils }
  169.   DateTime: TDateTime;
  170.   RecBuf: PChar;
  171. begin
  172.   RecBuf := ActiveBuffer;
  173.   if State = dsCalcFields then
  174.     RecBuf := CalcBuffer;
  175.  
  176.   if Field.FieldNo <> -1 then begin   { a physical field }
  177.  
  178.     { Check for a null value }
  179.     NullFlags := @RecBuf[FNullFlagsOffset];
  180.     Result := ((NullFlags^ and (1 shl (Field.FieldNo - 1))) = 0);
  181.  
  182.     { If value is not null }
  183.     if Result and Assigned(Buffer) then begin
  184.       FillChar(Buffer^, Field.DataSize, 0);
  185.       Offset := LongInt(FieldOffsets[Field.FieldNo - 1]);
  186.       DataSize := Field.DataSize;
  187.  
  188.       { Special handing for date/time fields }
  189.       if Field.DataType in [ftDateTime, ftDate, ftTime] then begin
  190.         Move(RecBuf[Offset], DateTime, DataSize);
  191.         TimeStamp := DateTimeToTimeStamp(DateTime);
  192.         case Field.DataType of
  193.           ftDate: TDateTimeRec(Buffer^).Date := TimeStamp.Date;
  194.           ftTime: TDateTimeRec(Buffer^).Time := TimeStamp.Time;
  195.           else
  196.             TDateTimeRec(Buffer^).DateTime := TimeStampToMSecs(TimeStamp);
  197.         end;
  198.       end
  199.       else begin
  200.         if Field.DataType = ftString then begin
  201.           DataSize := Byte(RecBuf[Offset]);
  202.           Inc(Offset);
  203.         end;
  204.         Move(RecBuf[Offset], Buffer^, DataSize);
  205.       end;
  206.     end;
  207.   end
  208.   else begin   { a calculated field }
  209.     Offset := FCalcFieldsOffset + Field.Offset;
  210.     Result := not Boolean(RecBuf[Offset]);
  211.     if Result and Assigned(Buffer) then begin
  212.       Move(RecBuf[Offset + 1], Buffer^, Field.DataSize);
  213.     end;
  214.   end;
  215. end;
  216.  
  217. function TMyDataSet.GetRecNo: Integer;
  218. begin
  219.   { Because of Delphi's internal record buffering, we must read the stored
  220.     record number, not the current physical file position }
  221.   Result := PExtraRecInfo(ActiveBuffer + FExtraRecInfoOffset)^.RecordNumber;
  222. end;
  223.  
  224. function TMyDataSet.GetRecord(Buffer: PChar; GetMode: TGetMode; DoCheck: Boolean): TGetResult;
  225. var
  226.   FilePosition: LongInt;
  227. begin
  228.   Result := grOk;
  229.   case GetMode of
  230.     gmCurrent:
  231.       begin
  232.         Seek(FInternalFile, FilePos(FInternalFile) - FRecSize);
  233.         BlockRead(FInternalFile, Buffer^, FRecSize);
  234.         if Byte(Buffer^) <> 0 then  { deleted rec? }
  235.           Result := grError;
  236.       end;
  237.     gmNext:
  238.       { read next record, skipping deleted records }
  239.       repeat
  240.         if System.Eof(FInternalFile) then
  241.           Result := grEOF
  242.         else
  243.           BlockRead(FInternalFile, Buffer^, FRecSize);
  244.       until (Result <> grOk) or (Byte(Buffer^) = 0);
  245.     gmPrior:
  246.       repeat
  247.         FilePosition := FilePos(FInternalFile);
  248.         if FilePosition < (2 * FRecSize) then
  249.           Result := grBOF
  250.         else begin
  251.           if Eof then
  252.             Seek(FInternalFile, FileSize(FInternalFile) - FRecSize)
  253.           else
  254.             Seek(FInternalFile, FilePosition - (2 * FRecSize));
  255.           BlockRead(FInternalFile, Buffer^, FRecSize);
  256.         end;
  257.       until (Result <> grOk) or (Byte(Buffer^) = 0);
  258.     else
  259.       Result := grError;
  260.   end;
  261.  
  262.   if Result = grOk then begin
  263.     GetCalcFields(Buffer);
  264.     with PExtraRecInfo(Buffer + FExtraRecInfoOffset)^ do begin
  265.       RecordNumber := (FilePos(FInternalFile) div FRecSize) - 1;
  266.       BookmarkFlag := bfCurrent;
  267.       SetBookmarkData(Buffer, @RecordNumber);
  268.     end;
  269.   end;
  270. end;
  271.  
  272. function TMyDataSet.GetRecordCount: Integer;
  273. begin
  274.   { Note this returns the total number of records, including
  275.     deleted records.  Ideally we would store the number of "active"
  276.     records in a file header record. }
  277.   Result := FileSize(FInternalFile) div FRecSize;
  278. end;
  279.  
  280. function TMyDataSet.GetRecordSize: Word;
  281. begin
  282.   Result := FRecSize;
  283. end;
  284.  
  285. procedure TMyDataSet.InternalAddRecord(Buffer: Pointer; Append: Boolean);
  286. begin
  287.   { In our case, is doesn't matter if the record is being appended,
  288.     inserts and appends both get written to the end of the file. }
  289.   Byte(Buffer^) := 0;  { reset deleted flag as a precaution }
  290.   Seek(FInternalFile, FileSize(FInternalFile));
  291.   BlockWrite(FInternalFile, Buffer^, FRecSize);
  292. end;
  293.  
  294. procedure TMyDataSet.InternalClose;
  295. begin
  296.  
  297.   { Destroy the TField components if no persistent fields }
  298.   if DefaultFields then DestroyFields;
  299.  
  300.   { InternalClose is called by the Fields Editor in design mode, so
  301.     the actual table may not be open. }
  302.   if FCursorOpen then
  303.     CloseFile(FInternalFile);
  304.   FCursorOpen := False;
  305. end;
  306.  
  307. procedure TMyDataSet.InternalDelete;
  308. var
  309.   DelFlag: Byte;
  310.   FilePosition: LongInt;
  311. begin
  312.   FilePosition := FilePos(FInternalFile) - FRecSize;
  313.   Seek(FInternalFile, FilePosition);
  314.   DelFlag := 255;
  315.   BlockWrite(FInternalFile, DelFlag, 1);
  316.   Seek(FInternalFile, FilePosition + FRecSize);
  317. end;
  318.  
  319. procedure TMyDataSet.InternalEdit;
  320. begin
  321.   { Refresh the current record }
  322.   Seek(FInternalFile, FilePos(FInternalFile) - FRecSize);
  323.   BlockRead(FInternalFile, ActiveBuffer^, FRecSize);
  324. end;
  325.  
  326. procedure TMyDataSet.InternalFirst;
  327. begin
  328.   Seek(FInternalFile, 0);
  329. end;
  330.  
  331. procedure TMyDataSet.InternalInitFieldDefs;
  332. var
  333.   DictFile: TextFile;
  334.   DictRec: ShortString;
  335.   FieldNo: Integer;
  336.   FieldName: ShortString;
  337.   Required: Boolean;
  338.   DataType: TFieldType;
  339.   Size: Word;
  340.   ActualSize: Word;
  341.  
  342.   procedure GetNextAttribute(Rec: ShortString; var Attribute, OutRec: ShortString);
  343.   var
  344.     I: Integer;
  345.   begin
  346.     I := 1;
  347.     Attribute := '';
  348.     OutRec := '';
  349.     if Rec = '' then Exit;
  350.     while (I <= Length(Rec)) and (Rec[I] <> ',') do begin
  351.       if not (Rec[I] in [' ', #9]) then
  352.         Attribute := Attribute + Rec[I];
  353.       Inc(I);
  354.     end;
  355.     if I < Length(Rec) then
  356.       OutRec := Copy(Rec, I + 1, Length(Rec));
  357.   end;
  358.  
  359.   procedure ParseDictRec;
  360.   var
  361.     DataTypeStr: ShortString;
  362.     TempSize: Integer;
  363.     Attribute: ShortString;
  364.   begin
  365.     { Get field name }
  366.     GetNextAttribute(DictRec, FieldName, DictRec);
  367.     if FieldName = '' then Exit;
  368.     { Get data type }
  369.     GetNextAttribute(DictRec, DataTypeStr, DictRec);
  370.     { Get size }
  371.     GetNextAttribute(DictRec, Attribute, DictRec);
  372.     TempSize := 0;
  373.     if Attribute <> '' then TempSize := StrToInt(Attribute);
  374.     { Get null/not null }
  375.     GetNextAttribute(DictRec, Attribute, DictRec);
  376.     Attribute := Uppercase(Attribute);
  377.     Required := Attribute <> 'NULL';
  378.  
  379.     Size := 0;
  380.     ActualSize := 0;
  381.     DataTypeStr := Uppercase(DataTypeStr);
  382.     if DataTypeStr = 'SMALLINT' then begin
  383.       DataType := ftSmallInt;
  384.       ActualSize := SizeOf(SmallInt);
  385.     end
  386.     else if DataTypeStr = 'INTEGER' then begin
  387.       DataType := ftInteger;
  388.       ActualSize := SizeOf(Integer);
  389.     end
  390.     else if DataTypeStr = 'WORD' then begin
  391.       DataType := ftWord;
  392.       ActualSize := SizeOf(Word);
  393.     end
  394.     else if DataTypeStr = 'SINGLE' then begin
  395.       DataType := ftFloat;
  396.       ActualSize := SizeOf(Single);
  397.     end
  398.     else if DataTypeStr = 'DOUBLE' then begin
  399.       DataType := ftFloat;
  400.       ActualSize := SizeOf(Double);
  401.     end
  402.     else if DataTypeStr = 'STRING' then begin
  403.       DataType := ftString;
  404.       ActualSize := TempSize + 1;
  405.       Size := TempSize;
  406.     end
  407.     else if DataTypeStr = 'DATETIME' then begin
  408.       DataType := ftDateTime;
  409.       ActualSize := SizeOf(TDateTime);
  410.     end
  411.     else
  412.       DataType := ftUnknown;
  413.   end;
  414. begin
  415.   FieldDefs.Clear;
  416.   AssignFile(DictFile, ChangeFileExt(FTableName, '.DIC'));
  417.   Reset(DictFile);
  418.   try
  419.     FRecSize := 1;  {skip the delete flag field}
  420.     FieldNo := 0;
  421.     while not System.Eof(DictFile) do begin
  422.       ReadLn(DictFile, DictRec);
  423.       Inc(FieldNo);
  424.       ParseDictRec;
  425.       if FieldName <> '' then begin
  426.         FieldOffsets.Add(Pointer(FRecSize));  { store field offset }
  427.         Inc(FRecSize, ActualSize);            { compute our record size }
  428.         TFieldDef.Create(FieldDefs, FieldName, DataType, Size, Required, FieldNo);
  429.       end;
  430.     end;
  431.     FNullFlagsOffset := FRecSize;
  432.     Inc(FRecSize, SizeOf(LongInt));  { Record size includes null flags space }
  433.   finally
  434.     CloseFile(DictFile);
  435.   end;
  436. end;
  437.  
  438. procedure TMyDataSet.InternalInitRecord(Buffer: PChar);
  439. begin
  440.   FillChar(Buffer^, FRecBufSize, #0);
  441. end;
  442.  
  443. procedure TMyDataSet.InternalGotoBookmark(Bookmark: Pointer);
  444. { position physical file to bookmarked record }
  445. begin
  446.   { Position AFTER the record, as though we just read it }
  447.   Seek(FInternalFile, (TBookmarkInfo(Bookmark^) + 1) * FRecSize);
  448. end;
  449.  
  450. procedure TMyDataSet.InternalLast;
  451. begin
  452.   Seek(FInternalFile, FileSize(FInternalFile));  { force eof condition }
  453. end;
  454.  
  455. procedure TMyDataSet.InternalOpen;
  456. begin
  457.   AssignFile(FInternalFile, FTableName);
  458.   Reset(FInternalFile, 1);   { Open a file of bytes }
  459.   FCursorOpen := True;
  460.  
  461.   InternalInitFieldDefs;              { Populate FieldDefs from external dict }
  462.   if DefaultFields then CreateFields; { Populate Fields from FieldDefs }
  463.   BindFields(True);
  464.  
  465.   BookmarkSize := SizeOf(TBookmarkInfo);
  466.  
  467.   { Compute offsets to various record buffer segments }
  468.   FCalcFieldsOffset := FRecSize;
  469.   FExtraRecInfoOffset := FCalcFieldsOffset + CalcFieldsSize;
  470.   FBookmarkOffset := FExtraRecInfoOffset + SizeOf(TExtraRecInfo);
  471.   FRecBufSize := FBookmarkOffset + BookmarkSize;
  472. end;
  473.  
  474. procedure TMyDataSet.InternalPost;
  475. begin
  476.   case State of
  477.     dsEdit:
  478.       begin
  479.         Seek(FInternalFile, FilePos(FInternalFile) - FRecSize);
  480.         BlockWrite(FInternalFile, ActiveBuffer^, FRecSize);
  481.       end;
  482.     dsInsert:
  483.       begin
  484.         Byte(ActiveBuffer^) := 0;  { reset deleted flag }
  485.         Seek(FInternalFile, FileSize(FInternalFile));
  486.         BlockWrite(FInternalFile, ActiveBuffer^, FRecSize);
  487.       end;
  488.   end;
  489. end;
  490.  
  491. procedure TMyDataSet.InternalRefresh;
  492. begin
  493.   { This is where we would refresh any buffers we are using
  494.     between the data and TDataSet's internal record buffers. }
  495. end;
  496.  
  497. procedure TMyDataSet.InternalSetToRecord(Buffer: PChar);
  498. begin
  499.   InternalGotoBookmark(Buffer + FBookmarkOffset);
  500. end;
  501.  
  502. function TMyDataSet.IsCursorOpen: Boolean;
  503. begin
  504.   Result := FCursorOpen;
  505. end;
  506.  
  507. procedure TMyDataSet.SetBookmarkData(Buffer: PChar; Data: Pointer);
  508. begin
  509.   Move(Data^, Buffer[FBookmarkOffset], BookmarkSize);
  510. end;
  511.  
  512. procedure TMyDataSet.SetBookmarkFlag(Buffer: PChar; Value: TBookmarkFlag);
  513. begin
  514.   PExtraRecInfo(Buffer + FExtraRecInfoOffset).BookmarkFlag := Value;
  515. end;
  516.  
  517. procedure TMyDataSet.SetFieldData(Field: TField; Buffer: Pointer);
  518. var
  519.   Offset,
  520.   DataSize: Integer;
  521.   StrBuff: ShortString;
  522.   NullFlags: ^LongInt;
  523.   TimeStamp: TTimeStamp; { TTimeStamp is declared in SysUtils }
  524.   DateTime: TDateTime;
  525. begin
  526.   if Field.FieldNo <> -1 then begin   { a physical field }
  527.     { Cannot set fields while in OnCalcFields handler }
  528.     if State = dsCalcFields then DatabaseError(SNotEditing);
  529.  
  530.     Offset := LongInt(FieldOffsets[Field.FieldNo - 1]);
  531.     DataSize := Field.DataSize;  {?? need this? }
  532.     { Current null flags }
  533.     NullFlags := @ActiveBuffer[FNullFlagsOffset];
  534.  
  535.     if not Assigned(Buffer) then begin
  536.       { If setting field to null, clear the field data
  537.         and set the null flag }
  538.       FillChar(ActiveBuffer[Offset], DataSize, #0);
  539.       NullFlags^ := NullFlags^ or (1 shl (Field.FieldNo - 1));
  540.     end
  541.     else begin
  542.  
  543.       { Special handing for date/time fields }
  544.       if Field.DataType in [ftDateTime, ftDate, ftTime] then begin
  545.         case Field.DataType of
  546.           ftDate:
  547.             begin
  548.               TimeStamp.Time := 0;
  549.               TimeStamp.Date := TDateTimeRec(Buffer^).Date;
  550.             end;
  551.           ftTime:
  552.             begin
  553.               TimeStamp.Time := TDateTimeRec(Buffer^).Time;
  554.               TimeStamp.Date := DateDelta;
  555.             end;
  556.           else
  557.             try
  558.               TimeStamp := MSecsToTimeStamp(TDateTimeRec(Buffer^).DateTime);
  559.             except
  560.               TimeStamp.Time := 0;
  561.               TimeStamp.Date := 0;
  562.             end;
  563.         end;
  564.         DateTime := TimeStampToDateTime(TimeStamp);
  565.         Move(DateTime, ActiveBuffer[Offset], SizeOf(TDateTime));
  566.       end
  567.       else if Field.DataType = ftString then begin
  568.         StrBuff := StrPas(Buffer);
  569.         Move(StrBuff, ActiveBuffer[Offset], DataSize);
  570.       end
  571.       else
  572.         Move(Buffer^, ActiveBuffer[Offset], DataSize);
  573.       { Set flag to nonnull }
  574.       NullFlags^ := NullFlags^ and not (1 shl (Field.FieldNo - 1));
  575.     end;
  576.   end
  577.   else begin   { a calculated field }
  578.     Offset := FCalcFieldsOffset + Field.Offset;
  579.     Boolean(CalcBuffer[0]) := not Assigned(Buffer);
  580.     if Assigned(Buffer) then begin
  581.       Move(Buffer^, CalcBuffer[Offset + 1], Field.DataSize);
  582.     end;
  583.   end;
  584.  
  585.   if not (State in [dsCalcFields]) then
  586.     DataEvent(deFieldChange, Longint(Field));
  587. end;
  588.  
  589. end.
  590.